<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<body></body>
<script>
function getCanvas2DContext(name)
{
  var title = document.createElement("b");
  title.innerText = name;
  document.body.appendChild(title);
  var canvas = document.createElement("canvas");
  canvas.setAttribute("width", 100);
  canvas.setAttribute("style", "border: 1px solid black; display:block;");
  canvas.setAttribute("height", 30);
  document.body.appendChild(canvas);
  var context = canvas.getContext("2d", { alpha: false });
  context.clearRect(0,0, 100, 60);
  context.fillStyle = "#FFFFFF";
  context.fillRect(0,0,100, 60);
  context.fillStyle = "#000000";
  context.clearRect(0,0,100, 30);
  context.font = "10px Arial";
  return context;
}

/*
  This method checks to see if something was rendered on the canvas
  at the passed in location. The purposed of these tests are not to 
  accuratley validate rendering results but to check that mutations
  on the CanvasFormattedText succeed, hence the all sanity check 
  validates is that there are expected number of rendered pixels.

  After checking, this method also draws a line over the pixels that 
  we tested for visual diagnostics.
*/
function sanity_check_rendering(context, x, y, expect_content=true)
{
   var pixels = context.getImageData(x, y, 1, 10);
   context.fillStyle = "#008800";
   context.fillRect(x, y, 1, 10);
   context.fillStyle = "#000000";
   var transparent_pixels = 0;
   for (var i = 0; i < 10; i++)
   {
     if (pixels.data[i*4+3] == 0)
      transparent_pixels++
   }
   // Atleast 20% of the pixels in the test patch should have content.
   if (expect_content)
   {
      assert_less_than(transparent_pixels,
       8,
       "Missing content. ["+ pixels.data + "] #Unfilled pixels");
   }
   else
   {
      assert_equals(transparent_pixels,
       10,
       "Unexpected content.[" + pixels.data + "] #Unfilled pixels");
   }
}

function assert_exception(f, exception_name, error_message)
{
  try {
    assert_equals(f(), null, message);
  }
  catch (e) {
    assert_equals(e.name, exception_name,
    "Exception Type mismatch for " + error_message)
  }
}

test(function(t) {
  var text = new CanvasFormattedText();
  text.appendRun(new CanvasFormattedTextRun("A"));
  text.appendRun(new CanvasFormattedTextRun("B"));
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 10, 0);
  assert_equals(text.length, 2, "Unexpected length for CanvasFormattedText");
}, 'Append: before fillFormattedText');

test(function(t) {
  var text = new CanvasFormattedText();
  text.appendRun(new CanvasFormattedTextRun("A"));
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 10, 0, false);
  text.appendRun(new CanvasFormattedTextRun("B"));
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 10, 0);

  assert_equals(text.length, 2, "Unexpected length for CanvasFormattedText");
}, 'Append: after fillFormattedText');

test(function(t) {
  var text = new CanvasFormattedText();
  var run = new CanvasFormattedTextRun("B");
  text.appendRun(run);
  assert_exception(()=> { text.appendRun(run); },
   "InvalidModificationError",
   "Expect that an already inserted run cant be appended.");

   // Check the the canvas formatted text can still render the existing run.
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);
  // Ensure second run is not rendered.
  sanity_check_rendering(context, 10, 0, false);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
}, 'Append: Ensure duplicate appends fail');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  text.appendRun(runA);

  assert_exception(()=> { var c = text[-1]; },
   "ReferenceError",
   "can't get runs at  -1.");
   assert_exception(()=> { var c = text[100]; },
   "ReferenceError",
   "can't get runs at 100.");
  assert_equals(text[0], runA, "Getter should return the same object.");
}, 'GetRun: Test getRun range checks');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");

  assert_exception(()=> { text[0] = run; },
   "ReferenceError",
   "can't set runs at 0.");
  text.appendRun(runA);

  var runB = new CanvasFormattedTextRun("B");
  assert_exception(()=> { text[-1] = runB; },
   "ReferenceError",
   "can't set runs at  -1.");
   assert_exception(()=> { text[100] = runB; },
   "IndexSizeError",
   "can't set runs at 100.");
  text[0] = runB;
  assert_equals(text[0], runB, "Getter should return the same object.");

   // Check the the canvas formatted text can still render the new run.
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");

  // Check that we can go back to the previous run that was removed.
  text[0] = runA;
  context.fillFormattedText(text, 0, 15, 100);
  sanity_check_rendering(context, 2, 15);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
}, 'SetRun: Test setRun functionality');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  var runB = new CanvasFormattedTextRun("B");
  text.appendRun(runA);

  assert_exception(()=> { text[0] = runA; },
   "InvalidModificationError",
   "can't set any parented run.");

   var text2 = new CanvasFormattedText();
   text2.appendRun(runB);
   assert_exception(()=> { text2[0] = text[0]; },
   "InvalidModificationError",
   "can't set any parented run from another canvas formatted text.");
   
   assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
   assert_equals(text2.length, 1,
    "Unexpected length for CanvasFormattedText text2");

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);
}, 'SetRun: Test that parented runs can\'t be set');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  var runB = new CanvasFormattedTextRun("E");
  text.appendRun(runA);
   assert_exception(()=> { text.deleteRun(-1); },
   "IndexSizeError",
   "can't delete run at  -1.");
   assert_exception(()=> { text.deleteRun(100); },
   "IndexSizeError",
   "can't delete run at 100.");
   // Similar check for delete with range
   assert_exception(()=> { text.deleteRun(-1, 1); },
   "IndexSizeError",
   "can't delete runs at  -1.");
   assert_exception(()=> { text.deleteRun(100, 1); },
   "IndexSizeError",
   "can't delete runs at 100.");

   assert_exception(()=> { text.deleteRun(0, -1); },
   "IndexSizeError",
   "can't delete -ve number of runs");
   assert_exception(()=> { text.deleteRun(0, 0); },
   "ReferenceError",
   "can't delete 0 number of runs");
   assert_exception(()=> { text.deleteRun(0, 2); },
   "IndexSizeError",
   "can't delete 2 number of runs.");
   text.appendRun(runB);
   assert_exception(()=> { text.deleteRun(2, -1); },
   "IndexSizeError",
   "can't delete -ve number of runs");
   assert_exception(()=> { text.deleteRun(1, -1); },
   "IndexSizeError",
   "can't delete -ve number of runs");
}, 'DeleteRun: Verify index checks');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  var runB = new CanvasFormattedTextRun("E");
  text.appendRun(runA);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);

  text.deleteRun(0);
  assert_equals(text.length, 0, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 100);
  sanity_check_rendering(context, 2, 10, false);
}, 'DeleteRun: Verify delete operation');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  var runB = new CanvasFormattedTextRun("S");
  text.appendRun(runA);
  text.appendRun(runB);
  assert_equals(text.length, 2, "Unexpected length for CanvasFormattedText");
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);

  text.deleteRun(1);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 100);
  sanity_check_rendering(context, 10, 10, false);
}, 'DeleteRun: Verify delete operation with 2 runs');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E1");
  var runB = new CanvasFormattedTextRun("E2");
  text.appendRun(runA);
  text.appendRun(runB);  
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);

  text.deleteRun(0);
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 100);
  sanity_check_rendering(context, 14, 10, false);
}, 'DeleteRun: Verify delete operation with 2 runs deleting the first');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("ERun1");
  var runB = new CanvasFormattedTextRun("Run2");
  var runC = new CanvasFormattedTextRun("Run3");
  var runD = new CanvasFormattedTextRun("ERun4");
  text.appendRun(runA);
  text.appendRun(runB);
  text.appendRun(runC);
  text.appendRun(runD);

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);

  text.deleteRun(1, 2);
  assert_equals(text.length, 2, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 100);
  sanity_check_rendering(context, 80, 10, false);
}, 'DeleteRun: Delete Range');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun(".");
  var runB = new CanvasFormattedTextRun(".");
  var runC = new CanvasFormattedTextRun("EIns");
  text.appendRun(runA);
  text.appendRun(runB);

  assert_exception(()=> { text.insertRun(-1, runC); },
   "IndexSizeError",
   "can't insert run at  -1.");
   assert_exception(()=> { text.insertRun(100, runC); },
   "IndexSizeError",
   "can't insert run at 100.");
  assert_exception(()=> { text.insertRun(3, runC); },
   "IndexSizeError",
   "can't insert run at 3.");

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 6, 0, false);

  text.insertRun(1, runC);
  assert_equals(text.length, 3, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 200);
  sanity_check_rendering(context, 6, 10);
}, 'InsertRun: Validate Insertion Index');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun(".");
  var runB = new CanvasFormattedTextRun(".");
  var runC = new CanvasFormattedTextRun("EIns");
  text.appendRun(runA);
  text.appendRun(runB);

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 6, 0, false);

  text.insertRun(0, runC);
  assert_equals(text.length, 3, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 200);
  sanity_check_rendering(context, 4, 10);
}, 'InsertRun: Insert at Index 0');


test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun("E");
  text.insertRun(0, runA);

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0, true);

  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
}, 'InsertRun: Insert behaves like append');

test(function(t) {
  var text = new CanvasFormattedText();
  var runA = new CanvasFormattedTextRun(".");
  var runB = new CanvasFormattedTextRun(".");
  var runC = new CanvasFormattedTextRun("EIns");
  text.appendRun(runA);
  text.appendRun(runB);

  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 6, 0, false);

  assert_exception(()=> { text.insertRun(0, runA); },
   "InvalidModificationError",
   "can't set any parented run from another canvas formatted text.");
  assert_equals(text.length, 2, "Unexpected length for CanvasFormattedText");

  text.insertRun(1, runC);
  assert_equals(text.length, 3, "Unexpected length for CanvasFormattedText");
  assert_equals(text[2], runB,
   "Verify that runB that was at index 1 was shifted.");
  context.fillFormattedText(text, 0, 10, 200);
  sanity_check_rendering(context, 6, 10);
}, 'InsertRun: Insert at last Index');

test(function(t) {
  var text = new CanvasFormattedText();
  assert_exception(()=> { text.appendRun(null); },
   "TypeError",
   "cant append a null run.");
  assert_exception(()=> { text[0] = null; },
   "TypeError",
   "cant set a null run.");
   assert_exception(()=> { text.insertRun(0, null); },
   "TypeError",
   "cant insert a null run.");
}, 'NullRun: Check that append,insert,set are protected against null run');

test(function(t) {
  var text = new CanvasFormattedText("ERun1");
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
  var context = getCanvas2DContext(t.name);
  context.fillFormattedText(text, 0, 0, 100);
  sanity_check_rendering(context, 2, 0);

  var text = new CanvasFormattedText(new CanvasFormattedTextRun("ERun2"));
  assert_equals(text.length, 1, "Unexpected length for CanvasFormattedText");
  context.fillFormattedText(text, 0, 10, 100);
  sanity_check_rendering(context, 3, 10);
}, 'Constructor Variants');
</script>